/*
 * Copyright (c) 2007, 2021, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sun.org.apache.xml.internal.dtm.ref.sax2dtm;

import javax.xml.transform.Source;

import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMManager;
import com.sun.org.apache.xml.internal.dtm.DTMWSFilter;
import com.sun.org.apache.xml.internal.utils.IntStack;
import com.sun.org.apache.xml.internal.utils.IntVector;
import com.sun.org.apache.xml.internal.utils.StringVector;
import com.sun.org.apache.xml.internal.utils.XMLStringFactory;

import org.xml.sax.SAXException;

/**
 * This is a subclass of SAX2DTM which has been modified to meet the needs of
 * Result Tree Frameworks (RTFs). The differences are:
 * <p>
 * 1) Multiple XML trees may be appended to the single DTM. This means
 * that the root node of each document is _not_ node 0. Some code has
 * had to be deoptimized to support this mode of operation, and an
 * explicit mechanism for obtaining the Node Handle of the root node
 * has been provided.
 * <p>
 * 2) A stack of these documents is maintained, allowing us to "tail-prune" the
 * most recently added trees off the end of the DTM as stylesheet elements
 * (and thus variable contexts) are exited.
 * <p>
 * PLEASE NOTE that this class may be _heavily_ dependent upon the
 * internals of the SAX2DTM superclass, and must be maintained in
 * parallel with that code.  Arguably, they should be conditionals
 * within a single class... but they have deen separated for
 * performance reasons. (In fact, one could even argue about which is
 * the superclass and which is the subclass; the current arrangement
 * is as much about preserving stability of existing code during
 * development as anything else.)
 * <p>
 * %REVIEW% In fact, since the differences are so minor, I think it
 * may be possible/practical to fold them back into the base
 * SAX2DTM. Consider that as a future code-size optimization.
 */
public class SAX2RTFDTM extends SAX2DTM {
    /**
     * Set true to monitor SAX events and similar diagnostic info.
     */
    private static final boolean DEBUG = false;

    /**
     * Most recently started Document, or null if the DTM is empty.
     */
    private int m_currentDocumentNode = NULL;

    /**
     * Tail-pruning mark: Number of nodes in use
     */
    IntStack mark_size = new IntStack();
    /**
     * Tail-pruning mark: Number of data items in use
     */
    IntStack mark_data_size = new IntStack();
    /**
     * Tail-pruning mark: Number of size-of-data fields in use
     */
    IntStack mark_char_size = new IntStack();
    /**
     * Tail-pruning mark: Number of dataOrQName slots in use
     */
    IntStack mark_doq_size = new IntStack();
    /**
     * Tail-pruning mark: Number of namespace declaration sets in use
     * %REVIEW% I don't think number of NS sets is ever different from number
     * of NS elements. We can probabably reduce these to a single stack and save
     * some storage.
     */
    IntStack mark_nsdeclset_size = new IntStack();
    /**
     * Tail-pruning mark: Number of naespace declaration elements in use
     * %REVIEW% I don't think number of NS sets is ever different from number
     * of NS elements. We can probabably reduce these to a single stack and save
     * some storage.
     */
    IntStack mark_nsdeclelem_size = new IntStack();

    /**
     * Tail-pruning mark:  initial number of nodes in use
     */
    int m_emptyNodeCount;

    /**
     * Tail-pruning mark:  initial number of namespace declaration sets
     */
    int m_emptyNSDeclSetCount;

    /**
     * Tail-pruning mark:  initial number of namespace declaration elements
     */
    int m_emptyNSDeclSetElemsCount;

    /**
     * Tail-pruning mark:  initial number of data items in use
     */
    int m_emptyDataCount;

    /**
     * Tail-pruning mark:  initial number of characters in use
     */
    int m_emptyCharsCount;

    /**
     * Tail-pruning mark:  default initial number of dataOrQName slots in use
     */
    int m_emptyDataQNCount;

    public SAX2RTFDTM(DTMManager mgr, Source source, int dtmIdentity,
                      DTMWSFilter whiteSpaceFilter,
                      XMLStringFactory xstringfactory,
                      boolean doIndexing) {
        super(mgr, source, dtmIdentity, whiteSpaceFilter,
                xstringfactory, doIndexing);

        // NEVER track source locators for RTFs; they aren't meaningful. I think.
        // (If we did track them, we'd need to tail-prune these too.)
        //com.sun.org.apache.xalan.internal.processor.TransformerFactoryImpl.m_source_location;
        m_useSourceLocationProperty = false;
        m_sourceSystemId = (m_useSourceLocationProperty) ? new StringVector()
                : null;
        m_sourceLine = (m_useSourceLocationProperty) ? new IntVector() : null;
        m_sourceColumn = (m_useSourceLocationProperty) ? new IntVector() : null;

        // Record initial sizes of fields that are pushed and restored
        // for RTF tail-pruning.  More entries can be popped than pushed, so
        // we need this to mark the primordial state of the DTM.
        m_emptyNodeCount = m_size;
        m_emptyNSDeclSetCount = (m_namespaceDeclSets == null)
                ? 0 : m_namespaceDeclSets.size();
        m_emptyNSDeclSetElemsCount = (m_namespaceDeclSetElements == null)
                ? 0 : m_namespaceDeclSetElements.size();
        m_emptyDataCount = m_data.size();
        m_emptyCharsCount = m_chars.size();
        m_emptyDataQNCount = m_dataOrQName.size();
    }

    /**
     * Given a DTM, find the owning document node. In the case of
     * SAX2RTFDTM, which may contain multiple documents, this returns
     * the <b>most recently started</b> document, or null if the DTM is
     * empty or no document is currently under construction.
     * <p>
     * %REVIEW% Should we continue to report the most recent after
     * construction has ended? I think not, given that it may have been
     * tail-pruned.
     *
     * @return int Node handle of Document node, or null if this DTM does not
     * contain an "active" document.
     */
    public int getDocument() {
        return makeNodeHandle(m_currentDocumentNode);
    }

    /**
     * Given a node handle, find the owning document node, using DTM semantics
     * (Document owns itself) rather than DOM semantics (Document has no owner).
     * <p>
     * (I'm counting on the fact that getOwnerDocument() is implemented on top
     * of this call, in the superclass, to avoid having to rewrite that one.
     * Be careful if that code changes!)
     *
     * @param nodeHandle the id of the node.
     * @return int Node handle of owning document
     */
    public int getDocumentRoot(int nodeHandle) {
        for (int id = makeNodeIdentity(nodeHandle); id != NULL; id = _parent(id)) {
            if (_type(id) == DTM.DOCUMENT_NODE) {
                return makeNodeHandle(id);
            }
        }

        return DTM.NULL; // Safety net; should never happen
    }

    /**
     * Given a node identifier, find the owning document node.  Unlike the DOM,
     * this considers the owningDocument of a Document to be itself. Note that
     * in shared DTMs this may not be zero.
     *
     * @param nodeIdentifier the id of the starting node.
     * @return int Node identifier of the root of this DTM tree
     */
    protected int _documentRoot(int nodeIdentifier) {
        if (nodeIdentifier == NULL) return NULL;

        for (int parent = _parent(nodeIdentifier);
             parent != NULL;
             nodeIdentifier = parent, parent = _parent(nodeIdentifier))
            ;

        return nodeIdentifier;
    }

    /**
     * Receive notification of the beginning of a new RTF document.
     * <p>
     * %REVIEW% Y'know, this isn't all that much of a deoptimization. We
     * might want to consider folding the start/endDocument changes back
     * into the main SAX2DTM so we don't have to expose so many fields
     * (even as Protected) and carry the additional code.
     *
     * @throws SAXException Any SAX exception, possibly
     *                      wrapping another exception.
     * @see org.xml.sax.ContentHandler#startDocument
     */
    public void startDocument() throws SAXException {
        // Re-initialize the tree append process
        m_endDocumentOccured = false;
        m_prefixMappings = new java.util.Vector();
        m_contextIndexes = new IntStack();
        m_parents = new IntStack();

        m_currentDocumentNode = m_size;
        super.startDocument();
    }

    /**
     * Receive notification of the end of the document.
     * <p>
     * %REVIEW% Y'know, this isn't all that much of a deoptimization. We
     * might want to consider folding the start/endDocument changes back
     * into the main SAX2DTM so we don't have to expose so many fields
     * (even as Protected).
     *
     * @throws SAXException Any SAX exception, possibly
     *                      wrapping another exception.
     * @see org.xml.sax.ContentHandler#endDocument
     */
    public void endDocument() throws SAXException {
        charactersFlush();

        m_nextsib.setElementAt(NULL, m_currentDocumentNode);

        if (m_firstch.elementAt(m_currentDocumentNode) == NOTPROCESSED)
            m_firstch.setElementAt(NULL, m_currentDocumentNode);

        if (DTM.NULL != m_previous)
            m_nextsib.setElementAt(DTM.NULL, m_previous);

        m_parents = null;
        m_prefixMappings = null;
        m_contextIndexes = null;

        m_currentDocumentNode = NULL; // no longer open
        m_endDocumentOccured = true;
    }


    /**
     * "Tail-pruning" support for RTFs.
     * <p>
     * This function pushes information about the current size of the
     * DTM's data structures onto a stack, for use by popRewindMark()
     * (which see).
     * <p>
     * %REVIEW% I have no idea how to rewind m_elemIndexes. However,
     * RTFs will not be indexed, so I can simply panic if that case
     * arises. Hey, it works...
     */
    public void pushRewindMark() {
        if (m_indexing || m_elemIndexes != null)
            throw new java.lang.NullPointerException("Coding error; Don't try to mark/rewind an indexed DTM");

        // Values from DTMDefaultBase
        // %REVIEW% Can the namespace stack sizes ever differ? If not, save space!
        mark_size.push(m_size);
        mark_nsdeclset_size.push((m_namespaceDeclSets == null)
                ? 0
                : m_namespaceDeclSets.size());
        mark_nsdeclelem_size.push((m_namespaceDeclSetElements == null)
                ? 0
                : m_namespaceDeclSetElements.size());

        // Values from SAX2DTM
        mark_data_size.push(m_data.size());
        mark_char_size.push(m_chars.size());
        mark_doq_size.push(m_dataOrQName.size());
    }

    /**
     * "Tail-pruning" support for RTFs.
     * <p>
     * This function pops the information previously saved by
     * pushRewindMark (which see) and uses it to discard all nodes added
     * to the DTM after that time. We expect that this will allow us to
     * reuse storage more effectively.
     * <p>
     * This is _not_ intended to be called while a document is still being
     * constructed -- only between endDocument and the next startDocument
     * <p>
     * %REVIEW% WARNING: This is the first use of some of the truncation
     * methods.  If Xalan blows up after this is called, that's a likely
     * place to check.
     * <p>
     * %REVIEW% Our original design for DTMs permitted them to share
     * string pools.  If there any risk that this might be happening, we
     * can _not_ rewind and recover the string storage. One solution
     * might to assert that DTMs used for RTFs Must Not take advantage
     * of that feature, but this seems excessively fragile. Another, much
     * less attractive, would be to just let them leak... Nah.
     *
     * @return true if and only if the pop completely emptied the
     * RTF. That response is used when determining how to unspool
     * RTF-started-while-RTF-open situations.
     */
    public boolean popRewindMark() {
        boolean top = mark_size.empty();

        m_size = top ? m_emptyNodeCount : mark_size.pop();
        m_exptype.setSize(m_size);
        m_firstch.setSize(m_size);
        m_nextsib.setSize(m_size);
        m_prevsib.setSize(m_size);
        m_parent.setSize(m_size);

        m_elemIndexes = null;

        int ds = top ? m_emptyNSDeclSetCount : mark_nsdeclset_size.pop();
        if (m_namespaceDeclSets != null) {
            m_namespaceDeclSets.setSize(ds);
        }

        int ds1 = top ? m_emptyNSDeclSetElemsCount : mark_nsdeclelem_size.pop();
        if (m_namespaceDeclSetElements != null) {
            m_namespaceDeclSetElements.setSize(ds1);
        }

        // Values from SAX2DTM - m_data always has a reserved entry
        m_data.setSize(top ? m_emptyDataCount : mark_data_size.pop());
        m_chars.setLength(top ? m_emptyCharsCount : mark_char_size.pop());
        m_dataOrQName.setSize(top ? m_emptyDataQNCount : mark_doq_size.pop());

        // Return true iff DTM now empty
        return m_size == 0;
    }

    /**
     * @return true if a DTM tree is currently under construction.
     */
    public boolean isTreeIncomplete() {
        return !m_endDocumentOccured;
    }
}
